/**********************************************************************//**

 @file hc_grab.c
 @author zhangxuemin
 @date 2024/12/12

 @brief Support grab process param data value tool

**************************************************************************/

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <signal.h>
#include <time.h>
#include <stdarg.h>
#include <sys/time.h>

#define GET (1)
#define SET (2)
#define __TYPE(x) \
     ((char *)(&(x)))

#define VERSION__INFO "1.0.1"

#define printz(format, ...) \
                printf("[%s:%d]"format, __FUNCTION__, __LINE__, ##__VA_ARGS__)

static FILE *s_output_fp = NULL;
static int is_enable_debug_info = 1;
static int is_show_timestamp = 0;
static int is_show_num = 0;

extern char *optarg;
static int is_enable_tool = 1;
static const char *short_options = "hvtnqp:s:o:";
static const struct option long_options[] =
{
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'v'},
    {"time", no_argument, NULL, 't'},
    {"num", no_argument, NULL, 'n'},
    {"quit", no_argument, NULL, 'q'},
    {"pid", required_argument, NULL, 'p'},
    {"size", required_argument, NULL, 's'},
    {"output", required_argument, NULL, 'o'}
};

static const char *s_try_info = "Try `hc_grab -h` view handbook *** \r\n";
static const char *s_help_info =
" \
\r\n \
Usage: hc_grab [OPTION]... \r\n \
    eg: hc_grab -p 1234 -s 4096 -q \r\n \
\r\n \
Describe: grabs information about the specified process variable \r\n \
OPTION: \r\n \
  -h, --help        help info handbook print \r\n \
  -v, --version     version info print \r\n \
  -t, --time        timestamp info print \r\n \
  -n, --num         line num info print \r\n \
  -q, --quit        mask print information \r\n \
  -p, --pid         grab process pid (necessary parameter) [-p 1234],[--pid 1234] \r\n \
  -s, --size        grab memory size (unnecessary parameter, default size: 1024) [-s 4096],[--size 4096] \r\n \
  -o, --output      info output redirection path [-o \"/mnt/log.log\"],[--output \"/mnt/log.log\"] \r\n \
\r\n \
Cmd Option: \r\n \
  \\n     update new line \r\n \
  q      quit debug \r\n \
  h      help info handbook print \r\n \
  p      printf param value \r\n \
         usage: p [address] [format] \r\n \
         [address] : grab param address (necessary parameter) \r\n \
         [format]  : grab data output type (unnecessary parameter, default type: string fmt print) \r\n \
         eg: p 0x1468 d \r\n \
         format option: \r\n \
             [d]:    int type print \r\n \
             [s]:    string type print \r\n \
             [l]:    long long type print \r\n \
             [f]:    float type print \r\n \
             [F]:    double type print \r\n \
             [x]:    hex type print \r\n \
             [c]:    char type print \r\n \
             [p]:    point type print \r\n \
\r\n \
  s      set param value \r\n \
         usage: s [address] [format] [value] \r\n \
         [address] : set param address (necessary parameter) \r\n \
         [format]  : set data output type (necessary parameter) \r\n \
         [value]   : set param value (necessary parameter) \r\n \
         eg: s 0x1468 d 1024 \r\n \
             s 0x1576 s \"hello\" \r\n \
             s 0x5769 F 11.37 \r\n \
         format option: \r\n \
             [d]:    int type set \r\n \
             [s]:    string type set \r\n \
             [l]:    long long type set \r\n \
             [f]:    float type set \r\n \
             [F]:    double type set \r\n \
             [x]:    hex type set \r\n \
             [c]:    char type set \r\n \
             [p]:    point type set \r\n \
\r\n \
\r\n\
";

static void printm(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    vprintf(format, args);
    va_end(args);

    if (s_output_fp != NULL)
    {
        va_start(args, format);
        vfprintf(s_output_fp, format, args);
        va_end(args);
    }
}

static void signal_handler(int sig)
{
    is_enable_tool = 0;
}

static int gettm_to_str(char *strbuf, int strlen)
{
	time_t now = 0;
    struct timeval tv;
	struct tm gm_tm = {0};

	if (!strbuf || strlen <= 0)
	{
		return -1;
	}

    time(&now);
	gettimeofday(&tv, NULL);
	gmtime_r(&now, &gm_tm);
	snprintf(strbuf, strlen, "%04d-%02d-%02d %02d:%02d:%02d:%06ld",
            gm_tm.tm_year+1900, gm_tm.tm_mon+1, gm_tm.tm_mday,
			gm_tm.tm_hour, gm_tm.tm_min, gm_tm.tm_sec ,tv.tv_usec);

	return 0;
}

static char *get_info_show(void)
{
    char tm_buf[32] = {0};
    int info_buff_len = 0;
    static char info_buff[1024] = {0};

    if (is_show_num)
    {
        info_buff_len += snprintf(info_buff + info_buff_len, sizeof(info_buff) - info_buff_len, " %d ", is_show_num);
        is_show_num++;
    }

    if (is_show_timestamp)
    {
        gettm_to_str(tm_buf, sizeof(tm_buf));
        info_buff_len += snprintf(info_buff + info_buff_len, sizeof(info_buff) - info_buff_len, "[%s]", tm_buf);
    }

    info_buff_len += snprintf(info_buff + info_buff_len, sizeof(info_buff) - info_buff_len, "(%s)# ", "hc_grab");

    return info_buff;
}

static void clear_input_buffer(void)
{
    int tmp = 0;

    while ((tmp = getchar()) != '\n' && tmp != EOF);
}

int main(int argc, char *argv[])
{
    int fd = 0;
    int pid = 0;
    char fmt = 's';
    int read_buf_size = 1024;
    char *p_read_buf = NULL;
    int address = 0x0000;
    char filename[128] = {0};
    int opt = 0;
    int long_index = 0;
    int help_exit_flag = 0;
    int ret = 0;
    int cmd = 0;
    int mode = 0;
    char input_buf[128] = {0};
    char *endptr = NULL;

    signal(SIGINT, signal_handler);

    while (-1 != (opt = getopt_long(argc, argv, short_options, long_options, &long_index)))
    {
        switch (opt)
        {
            case 'h':
                printm("%s", s_help_info);
                help_exit_flag = 1;
                break;
            case 'v':
                printm("version info: %s \r\n", VERSION__INFO);
                help_exit_flag = 1;
                break;
            case 't':
                is_show_timestamp = 1;
                break;
            case 'n':
                is_show_num = 1;
                break;
            case 'q':
                is_enable_debug_info = 0;
                break;
            case 'p':
                pid = atoi(optarg);
                break;
            case 's':
                read_buf_size = atoi(optarg);
                break;
            case 'o':
                s_output_fp = fopen(optarg, "a+");
                if (!s_output_fp)
                {
                    perror("open");
                    return -1;
                }
                break;
            default:
                printm("intput param err, See the help screen for details \r\n");
                printm("%s", s_help_info);
                help_exit_flag = 1;
                break;
        }
    }

    if (help_exit_flag)
    {
        return 0;
    }

    if (is_enable_debug_info)
    {
        printm("build time : %s %s \r\n", __DATE__, __TIME__);
        printm("version info : %s \r\n", VERSION__INFO);
        printm("input param : pid[%d] read_size[%d] \r\n", pid, read_buf_size);
    }

    if (!pid)
    {
        printm("%s", s_try_info);
        return -1;
    }

    if (read_buf_size > 0)
    {
        p_read_buf = malloc(read_buf_size);
        if (NULL == p_read_buf)
        {
            perror("malloc");
            goto end;
        }
    }

    snprintf(filename, sizeof(filename), "/proc/%d/mem", pid);
    fd = open(filename, O_RDWR);
    if (fd == -1)
    {
        perror("open");
        goto end;
    }

start:
    while (is_enable_tool)
    {
        printm("%s", get_info_show());
        while (EOF != (cmd = getchar()))
        {
            switch (cmd)
            {
                case '\n':
                {
                    goto start;
                    break;
                }
                case 'h':
                {
                    printm("%s", s_help_info);
                    clear_input_buffer();
                    goto start;
                    break;
                }
                case 'q':
                {
                    is_enable_tool = 0;
                    clear_input_buffer();
                    goto start;
                    break;
                }
                case 'p':
                {
                    ret = scanf("%x %c", &address, &fmt);
                    if (2 != ret)
                    {
                        printm("%s", s_try_info);
                        goto end;
                    }
                    mode = GET;
                    goto grab;
                    break;
                }
                case 's':
                {
                    ret = scanf("%x %c %s", &address, &fmt, input_buf);
                    if (3 != ret)
                    {
                        printm("%s", s_try_info);
                        goto end;
                    }
                    mode = SET;
                    goto grab;
                    break;
                }
                default:
                {
                    printm("%s", s_try_info);
                    goto end;
                    break;
                }
            }
        }

grab:
        if (!address)
        {
            printm("[address err] %s \r\n", s_try_info);
            continue;
        }

        if (lseek(fd, address, SEEK_SET) == -1)
        {
            perror("lseek");
            goto end;
        }

        if (GET == mode)
        {
            if (read(fd, p_read_buf, read_buf_size) == -1)
            {
                perror("read");
                goto end;
            }

            switch (fmt)
            {
                case 'd':
                {
                    printm("%s Value at address[0x%02x]: (int)[%d] \r\n", get_info_show(), address, *((int *)p_read_buf));
                    break;
                }
                case 'l':
                {
                    printm("%s Value at address[0x%02x]: (long long)[%ld] \r\n", get_info_show(), address, *((long long *)p_read_buf));
                    break;
                }
                case 'f':
                {
                    printm("%s Value at address[0x%02x]: (float)[%f] \r\n", get_info_show(), address, *((float *)p_read_buf));
                    break;
                }
                case 'F':
                {
                    printm("%s Value at address[0x%02x]: (double)[%lf] \r\n", get_info_show(), address, *((double *)p_read_buf));
                    break;
                }
                case 's':
                {
                    printm("%s Value at address[0x%02x]: (string)[%s] \r\n", get_info_show(), address, p_read_buf);
                    break;
                }
                case 'x':
                {
                    printm("%s Value at address[0x%02x]: (hex)[0x%02x] \r\n", get_info_show(), address, *((int *)p_read_buf));
                    break;
                }
                case 'c':
                {
                    printm("%s Value at address[0x%02x]: (char)[%c] \r\n", get_info_show(), address, *p_read_buf);
                    break;
                }
                case 'p':
                {
                    printm("%s Value at address[0x%02x]: (pointer)[%p] \r\n", get_info_show(), address, *((long *)p_read_buf));
                    break;
                }
                default:
                {
                    printm("%s Value at address[0x%02x]: (default)[%s] \r\n", get_info_show(), address, p_read_buf);
                    break;
                }
            }
        }
        else if (SET == mode)
        {
            switch (fmt)
            {
                case 'd':
                {
                    int ret = atoi(input_buf);
                    if (write(fd, __TYPE(ret), sizeof(int)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'l':
                {
                    long long ret = strtoll(input_buf, &endptr, 10);
                    if (write(fd, __TYPE(ret), sizeof(long long)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'f':
                {
                    float ret = strtof(input_buf, &endptr);
                    if (write(fd, __TYPE(ret), sizeof(float)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'F':
                {
                    double ret = strtod(input_buf, &endptr);
                    if (write(fd, __TYPE(ret), sizeof(double)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 's':
                {
                    if (write(fd, input_buf, strlen(input_buf) + 1) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'x':
                {
                    int ret = atoi(input_buf);
                    if (write(fd, __TYPE(ret), sizeof(int)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'c':
                {
                    if (write(fd, input_buf, 1) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                case 'p':
                {
                    int ret = atoi(input_buf);
                    if (write(fd, __TYPE(ret), sizeof(long)) == -1)
                    {
                        perror("write");
                        goto end;
                    }
                    break;
                }
                default:
                {
                    printm("%s Value at address[0x%02x]: (default)[%s] \r\n", get_info_show(), address, p_read_buf);
                    break;
                }
            }
        }
        clear_input_buffer();
        mode = 0;
    }

end:
    if (fd)
    {
        close(fd);
        fd = 0;
    }

    if (p_read_buf)
    {
        free(p_read_buf);
        p_read_buf = NULL;
    }

	if (s_output_fp)
	{
		fclose(s_output_fp);
		s_output_fp = NULL;
	}

    return 0;
}
